package com.siao.net.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author SiaoLeon
 * Socket请求
 */
public class SocketRequest {

	private static final Logger logger = LoggerFactory.getLogger(SocketRequest.class);

	/**
	 * 数据解析器
	 */
	public IDataResolver dataResolver;

	/**
	 * 读取缓存大小
	 */
	private int readBufferSize;

	/**
	 * 请求数据包
	 */
	private IDataPack requestData;

	/**
	 * 返回数据包
	 */
	private Object receiveData;

	/**
	 * 是否等待结果
	 */
	private Boolean isWaitResult;

	/**
	 * 构造函数
	 */
	public SocketRequest() {
		this(2048);
	}

	/**
	 * 构造函数
	 * @param readBufferSize
	 */
	public SocketRequest(int readBufferSize) {
		this(readBufferSize, null);
	}

	/**
	 * 构造函数
	 * @param dataResolver
	 */
	public SocketRequest(IDataResolver dataResolver) {
		this(2048, dataResolver);
	}

	/**
	 * 构造函数
	 * @param readBufferSize
	 * @param dataResolver
	 */
	public SocketRequest(int readBufferSize, IDataResolver dataResolver) {
		this.readBufferSize = readBufferSize;
		this.dataResolver = dataResolver;
	}

	/**
	 * 发送数据请求，并等待返回数据
	 * @param address
	 * @param data
	 * @return
	 */
	public synchronized Object request(InetSocketAddress address, IDataPack data) {
		return this.request(address, data, this.dataResolver);
	}

	/**
	 * 发送数据请求，并等待返回数据
	 * @param address
	 * @param data
	 * @param timeout
	 * @return
	 */
	public synchronized Object request(InetSocketAddress address, IDataPack data, int timeout) {
		return this.request(address, data, this.dataResolver, timeout);
	}

	/**
	 * 发送数据请求，并等待返回数据
	 * @param address
	 * @param data
	 * @param dataResolver
	 * @return
	 */
	public synchronized Object request(InetSocketAddress address, IDataPack data, IDataResolver dataResolver) {
		return this.request(address, data, dataResolver, 5000);
	}

	/**
	 * 发送数据请求，并等待返回数据
	 * @param address
	 * @param data
	 * @param dataResolver
	 * @param timeout
	 * @return
	 */
	public synchronized Object request(InetSocketAddress address, IDataPack data, IDataResolver dataResolver, int timeout) {
		return this.request(address, data, dataResolver, timeout, this.readBufferSize);
	}

	/**
	 * 发送数据请求，并等待返回数据
	 * @param address
	 * @param data
	 * @param dataResolver
	 * @param timeout
	 * @param readBufferSize
	 * @return
	 */
	public synchronized Object request(InetSocketAddress address, IDataPack data, IDataResolver dataResolver, int timeout, int readBufferSize) {
		this.requestData = data;
		this.receiveData = null;
		this.readBufferSize = readBufferSize;
		this.dataResolver = dataResolver;
		this.isWaitResult = true;

		this.connect(address, timeout);

		return this.receiveData;
	}

	/**
	 * 异步发送数据请求
	 * @param address
	 * @param data
	 */
	public void post(InetSocketAddress address, IDataPack data) {
		this.post(address, data, this.dataResolver);
	}

	/**
	 * 异步发送数据请求
	 * @param address
	 * @param data
	 * @param dataResolver
	 */
	public void post(InetSocketAddress address, IDataPack data, IDataResolver dataResolver) {
		this.requestData = data;
		this.dataResolver = dataResolver;
		this.isWaitResult = false;

		this.connect(address, 3000);
	}

	/**
	 * 连接
	 * @param timeout
	 */
	private void connect(InetSocketAddress address, int timeout) {
		try {

			AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
			channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
			channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
			channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
			channel.connect(address, channel, new ConnectionAcceptHandler());

			synchronized (this) {
				this.wait(timeout);
			}

			channel.shutdownInput();
			channel.shutdownOutput();
			channel.close();
		} catch (InterruptedException e) {
		} catch (IOException e) {
			logger.error("SocketClient have failed to connect: " + address + "," + e.getMessage());
		}
	}

	/**
	 * @author SiaoLeon
	 * 连接处理
	 */
	class ConnectionAcceptHandler implements CompletionHandler<Void, AsynchronousSocketChannel> {

		@Override
		public void completed(Void result, AsynchronousSocketChannel channel) {
			IDataPack data = SocketRequest.this.requestData;

			ByteBuffer buffer = (SocketRequest.this.dataResolver == null) ? ByteBuffer.wrap(data.read()) : SocketRequest.this.dataResolver.pack(data);
			buffer.position(0);

			channel.write(buffer, channel, new Writter());
		}

		@Override
		public void failed(Throwable exc, AsynchronousSocketChannel channel) {
			exc.printStackTrace();
			logger.error(exc.getMessage());

			synchronized (SocketRequest.this) {
				SocketRequest.this.notifyAll();
			}
		}

	}

	/**
	 * @author SiaoLeon
	 * 写入处理器
	 */
	private final class Writter implements CompletionHandler<Integer, AsynchronousSocketChannel> {

		@Override
		public void completed(Integer result, AsynchronousSocketChannel channel) {
			if (SocketRequest.this.isWaitResult) {
				ByteBuffer buffer = ByteBuffer.allocate(SocketRequest.this.readBufferSize);
				channel.read(buffer, buffer, new Reader());
			} else {
				synchronized (SocketRequest.this) {
					SocketRequest.this.notifyAll();
				}
			}
		}

		@Override
		public void failed(Throwable exc, AsynchronousSocketChannel channel) {
			synchronized (SocketRequest.this) {
				SocketRequest.this.notifyAll();
			}
			logger.error("SocketRequest have failed to write");
		}

	}

	/**
	 * @author SiaoLeon
	 * 读取处理器
	 */
	private final class Reader implements CompletionHandler<Integer, ByteBuffer> {

		@Override
		public void completed(Integer result, ByteBuffer buffer) {
			if (result < 0) {
				synchronized (SocketRequest.this) {
					SocketRequest.this.notifyAll();
					logger.error("SocketRequest be close in reading");
				}
				return;
			}

			buffer.flip();

			if (SocketRequest.this.dataResolver == null) {
				byte[] bytes = new byte[buffer.remaining()];
				buffer.get(bytes);
				SocketRequest.this.receiveData = bytes;
			} else {
				if (SocketRequest.this.dataResolver.isContains(buffer)) {
					SocketRequest.this.receiveData = SocketRequest.this.dataResolver.parse(buffer);
				}
			}

			buffer.compact();

			synchronized (SocketRequest.this) {
				SocketRequest.this.notifyAll();
			}
		}

		@Override
		public void failed(Throwable exc, ByteBuffer buffer) {
			synchronized (SocketRequest.this) {
				SocketRequest.this.notifyAll();
			}
			logger.error("SocketRequest have failed to read");
		}

	}

}
